package com.dbflow5.adapter;

import com.dbflow5.adapter.queriable.ListModelLoader;
import com.dbflow5.adapter.queriable.SingleModelLoader;
import com.dbflow5.config.DBFlowDatabase;
import com.dbflow5.config.DatabaseConfig;
import com.dbflow5.config.FlowManager;
import com.dbflow5.config.TableConfig;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.FlowCursor;
import com.dbflow5.query.OperatorGroup;
import com.dbflow5.query.SQLite;
import com.dbflow5.query.Select;

/**
 * Description: Provides a base retrieval class for all [Model] backed
 * adapters.
 */
public abstract class RetrievalAdapter<T> {

    public RetrievalAdapter(DBFlowDatabase databaseDefinition){
        init(databaseDefinition);
    }

    /**
     * Overrides the default implementation and allows you to provide your own implementation. Defines
     * how a single [T] is loaded.
     *
     * @param singleModelLoader The loader to use.
     */
    private SingleModelLoader<T> _singleModelLoader = null;

    public void setSingleModelLoader(SingleModelLoader<T> singleModelLoader){
        this._singleModelLoader = singleModelLoader;
    }

    public SingleModelLoader<T> getSingleModelLoader(){
        if(_singleModelLoader == null){
            _singleModelLoader = createSingleModelLoader();
            return _singleModelLoader;
        }
        return _singleModelLoader;
    }

    /**
     * @return A new [ListModelLoader], caching will override this loader instance.
     */
    /**
     * Overrides the default implementation and allows you to provide your own implementation. Defines
     * how a list of [T] are loaded.
     *
     * @param listModelLoader The loader to use.
     */
    private ListModelLoader<T> _listModelLoader = null;

    public void setListModelLoader(ListModelLoader<T> value){
        this._listModelLoader = value;
    }

    public ListModelLoader<T> getListModelLoader(){
        if(_listModelLoader == null){
            _listModelLoader = createListModelLoader();
        }
        return _listModelLoader;
    }

    protected TableConfig<T> tableConfig = null;

    /**
     * table
     *
     * @return the model class this adapter corresponds to
     */
    public abstract Class<T> table();

    /**
     * getNonCacheableSingleModelLoader
     *
     * @return A new instance of a [SingleModelLoader]. Subsequent calls do not cache
     * this object so it's recommended only calling this in bulk if possible.
     */
    public SingleModelLoader<T> getNonCacheableSingleModelLoader(){
        return new SingleModelLoader<T>(table());
    }

    /**
     * getNonCacheableListModelLoader
     *
     * @return A new instance of a [ListModelLoader]. Subsequent calls do not cache
     * this object so it's recommended only calling this in bulk if possible.
     */
    public ListModelLoader<T> getNonCacheableListModelLoader(){
        return new ListModelLoader<T>(table());
    }

    private void init(DBFlowDatabase databaseDefinition) {
        DatabaseConfig databaseConfig = FlowManager.getConfig().getConfigForDatabase(databaseDefinition.associatedDatabaseClassFile());
        if (databaseConfig != null) {
            tableConfig = databaseConfig.getTableConfigForTable(table());
            if (tableConfig != null) {
                _singleModelLoader = tableConfig.singleModelLoader;
                _listModelLoader = tableConfig.listModelLoader;
            }
        }
    }

    /**
     * Returns a new [model] based on the object passed in. Will not overwrite existing object.
     *
     * @param model model
     * @param databaseWrapper databaseWrapper
     * @return load
     */
    public T load(T model, DatabaseWrapper databaseWrapper){
        return getNonCacheableSingleModelLoader().load(databaseWrapper,
                        (Select.select().from(table()).where(getPrimaryConditionClause(model)).getQuery()));
    }

    /**
     * Converts the specified [FlowCursor] into a new [T]
     *
     * @param cursor The cursor to load into the model
     * @param wrapper The database instance to use.
     * @return loadFromCursor
     */
    public abstract T loadFromCursor(FlowCursor cursor, DatabaseWrapper wrapper);

    /**
     * SQLite.selectCountOf
     *
     * @param model The model to query values from
     * @param databaseWrapper databaseWrapper
     * @return True if it exists as a row in the corresponding database table
     */
    public boolean exists(T model, DatabaseWrapper databaseWrapper) {
        return SQLite.selectCountOf()
                .from(table())
                .where(getPrimaryConditionClause(model))
                .hasData(databaseWrapper);
    }

    /**
     * The clause that contains necessary primary conditions for this table.
     *
     * @param model The primary condition clause.
     * @return The clause that contains necessary primary conditions for this table.
     */
    public abstract OperatorGroup getPrimaryConditionClause(T model);

    /**
     * ListModelLoader
     *
     * @return A new [ListModelLoader], caching will override this loader instance.
     */
    protected ListModelLoader<T> createListModelLoader(){
        return new ListModelLoader<>(table());
    }

    /**
     * createSingleModelLoader
     *
     * @return A new [SingleModelLoader], caching will override this loader instance.
     */
    protected SingleModelLoader<T> createSingleModelLoader(){
        return new SingleModelLoader<>(table());
    }
}